package com.gitee.yusugar.core.util;

import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.gitee.yusugar.common.YuToolServiceException;
import com.gitee.yusugar.core.annotation.QueryField;
import lombok.Data;
import lombok.experimental.Accessors;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;

/**
 * @author Y-sugar
 * <p>
 * wrapper 构造器
 */
public class WrapperUtil {

    /**
     * 获取分页条件 - 当前页
     *
     * @param cur 当前页
     * @return 若传入的当前页为 null 则默认为 第一页
     */
    public static Integer getPageCur(Integer cur) {
        return (cur != null && cur > 0 ? cur : 1);
    }

    /**
     * 获取分页条件 - 每页显示数
     *
     * @param size 每页显示数
     * @return 若传入的每页显示数为 null 则默认为 -1（即不分页查询全部数据）
     */
    public static Integer getPageSize(Integer size) {
        return (size != null && size > 0 ? size : -1);
    }

    /**
     * 获取分页条件
     *
     * @param cur  当前页
     * @param size 每页显示数
     * @param <T>  泛型
     * @return 分页对象
     */
    public static <T> Page<T> buildPage(Integer cur, Integer size) {
        return new Page<>(getPageCur(cur), getPageSize(size));
    }

    private static <T, E> QueryWrapper<E> baseQuery(T obj, int obtainParentCount) {
        if (obj == null){
            return Wrappers.emptyWrapper();
        }
        QueryWrapper<E> wrapper = new QueryWrapper<>();

        List<PlusProperty> props = plusObjFieldToProps(obj, obtainParentCount);

        props.forEach(prop -> {
            // 获取条件类型
            QueryField.Type[] conditionType = prop.getConditionType();
            // 获取表字段名称
            String conditionColumn = prop.getConditionColumn();
            // 获取数据是否为空
            boolean isEmpty = !(prop.isEmpty());
            // 获取数据库字段名，默认为 POJO 字段名，若 conditionName 不为空，则使用 conditionName 里的值
            String fileName = StrUtil.isNotBlank(conditionColumn) ? conditionColumn : prop.getName();
            // 将 java bean 字段名，转成数据库字段名
            fileName = javaFieldToDbField(fileName);

            // 获取表别名
            String alias = prop.getConditionAlias();
            if (StrUtil.isNotBlank(alias)) {
                fileName = alias + "." + fileName;
            }

            // 获取字段的值
            Object data = prop.getData();

            for (QueryField.Type type : conditionType) {
                if (type != null) {
                    switch (type) {
                        case LIKE:
                            wrapper.like(isEmpty, fileName, data);
                            break;
                        case NE:
                            wrapper.ne(isEmpty, fileName, data);
                            break;
                        case GE:
                            wrapper.ge(isEmpty, fileName, data);
                            break;
                        case LE:
                            wrapper.le(isEmpty, fileName, data);
                            break;
                        case GT:
                            wrapper.gt(isEmpty, fileName, data);
                            break;
                        case LT:
                            wrapper.lt(isEmpty, fileName, data);
                            break;
                        case ASC:
                            wrapper.orderByAsc(isEmpty, fileName);
                            break;
                        case DESC:
                            wrapper.orderByDesc(isEmpty, fileName);
                            break;
                        default:
                            wrapper.eq(isEmpty, fileName, data);
                            break;
                    }
                }
            }
        });

        return wrapper;
    }

    public static <T> QueryWrapper<T> query(T obj, int obtainParentCount) {
        return baseQuery(obj, obtainParentCount);
    }

    public static <T, E> QueryWrapper<E> query(T obj, Class<E> cls, int obtainParentCount) {
        return baseQuery(obj, obtainParentCount);
    }

    public static <T, E> LambdaQueryWrapper<E> lambdaQuery(T obj, int obtainParentCount) {
        return WrapperUtil.<T, E>baseQuery(obj, obtainParentCount).lambda();
    }

    public static <T, E> LambdaQueryWrapper<E> lambdaQuery(T obj, Class<E> cls, int obtainParentCount) {
        return WrapperUtil.lambdaQuery(obj, obtainParentCount);
    }

    public static List<PlusProperty> plusObjFieldToProps(Object obj, int obtainParentCount) {
        List<PlusProperty> propertyList = new ArrayList<>();
        Class<?> cls = obj.getClass();

        Field[] fields = cls.getDeclaredFields();

        for (Field field : fields) {
            int fieldValue = field.getModifiers();
            if (Modifier.isStatic(fieldValue) || Modifier.isPublic(fieldValue) || Modifier.isFinal(fieldValue)) {
                continue;
            }

            PlusProperty prop = new PlusProperty();

            try {
                QueryField queryAnon = field.getAnnotation(QueryField.class);
                if (queryAnon != null) {
                    prop.setConditionColumn(queryAnon.column()).setConditionType(queryAnon.value())
                            .setConditionAlias(queryAnon.alias());

                    String name = field.getName();
                    Object fieldVal = cls.getMethod(ObjUtil.obtainGetMethod(name)).invoke(obj);

                    prop.setName(name).setData(fieldVal).setEmpty(fieldVal == null || fieldVal instanceof String && fieldVal.toString().trim().length() <= 0);
                    propertyList.add(prop);
                }
            } catch (Exception e) {
                throw new YuToolServiceException(e.getMessage());
            }
        }

        if (obtainParentCount > 0) {
            Class<?> superclass = cls.getSuperclass();
            if (!superclass.isAssignableFrom(Object.class)) {
                List<PlusProperty> parentProps = plusObjFieldToProps(JSONObject.parseObject(JSON.toJSONString(obj), superclass), --obtainParentCount);
                if (parentProps.size() > 0) {
                    propertyList.addAll(parentProps);
                }
            }
        }

        return propertyList;
    }

    @Data
    @Accessors(chain = true)
    public static class PlusProperty {

        private String conditionColumn;

        private QueryField.Type[] conditionType;

        private String conditionAlias;

        private String name;

        private boolean empty;

        private Object data;
    }

    private static String javaFieldToDbField(String javaField) {
        StringBuilder buffer = new StringBuilder();

        int len = javaField.length();

        for (int i = 0; i < len; i++) {
            char word = javaField.charAt(i);
            if (Character.isUpperCase(word)) {
                buffer.append("_");
            }
            buffer.append(Character.toLowerCase(word));
        }

        return buffer.toString();
    }

}
